package org.mashart.pink.m.gui;

import java.util.Vector;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.List;
import javax.microedition.midlet.MIDletStateChangeException;

import javax.microedition.lcdui.StringItem;
import org.mashart.pink.m.PinkrMIDlet;
import org.mashart.pink.m.gps.Receiver;
import org.mashart.pink.m.store.Preferences;

/**
 * Main UI class that handles user interactions with the application
 */
public class Gui implements CommandListener {
/**
 * TODO It is currently used by Receiver to display debug messages.
 *      Instead, what I'd like to have is kind of a <code>GuiListener</code>
 *      so to separate GUI from the rest of the code
 */

    /**
     * Preferences persistences storage
     */
    Preferences pref;
    private static final String kUser = "user";
    private static final String kDeviceType = "devType";
    private static final String kBtConnectionURL = "btUrl";
    private static final String tBuiltin = "in";
    private static final String tBluetooth = "bt";

    /**
     * Parent of the Gui: the very MIDlet class instance.
     * Initialized in <code>Gui()</code> constructor.
     */
    private PinkrMIDlet midlet;

    /**
     * Instance of GPS data reciver class.
     * Initialized in <code>Gui()</code> constructor.
     */
    private Receiver recv;

    /**
     * We keep reference to the mobile device display so not to call
     * <code>Display.getDisplay(midlet)</code> every time we need it.
     *
     * Used to display the current <code>Canvas</code> or <code>Form</code>
     * to the user.
     */
    private Display display;

    /**
     * Used to keep the last debug message and eventually display that
     * to the user
     */
    private StringItem debug;

    /**
     * Used to keep the time duration of a round
     */
    private StringItem clock;

    /**
     * Used to keep the time duration of a round
     */
    private StringItem duration;

    /**
     * Used to keep the user id
     */
    private StringItem user;

    /**
     * Used to keep the device name
     */
    private StringItem device;

    /**
     * Used to display a message on screen
     */
    private StringItem msg;

    /**
     * Forms
     */
    private Form noGpsForm;
    private Form readyForm;
    private Form startedForm;
    private Form stoppedForm;
    private Form msgForm;

    /**
     * main UI commands
     */
    private final Command EXIT_CMD = new Command("Exit", Command.EXIT, 2);
    private final Command START_SEARCH_CMD = new Command("Search", Command.OK, 1);
    private final Command CANCEL_SEARCH_CMD = new Command("Cancel", Command.BACK, 2);
    private final Command BT_DISCOVER_CMD = new Command("Refresh", Command.CANCEL, 1);
    private final Command SELECT_CMD = new Command("Select", Command.OK, 1);
    private final Command CONFIG_CMD = new Command("Config", Command.CANCEL, 1);
    private final Command PAUSE_CMD = new Command("Pause", Command.OK, 2);
    private final Command RESUME_CMD = new Command("Resume", Command.OK, 2);
    private final Command START_ROUND_CMD = new Command("Start", Command.OK, 2);
    private final Command STOP_ROUND_CMD = new Command("Stop", Command.CANCEL, 2);
    private final Command HIDE_CMD = new Command("Hide", Command.EXIT, 2);

    private final String DeviceTypesMenuTitle = new String("Choose a device type");
    private final String DevicesMenuTitle = new String("Choose a device");
    private final String InternalDeviceMenuItem = new String("Built-in GPS");
    private final String BtDeviceMenuItem = new String("Bluetooth GPS");

    /**
     * List of available BT devices
     */
//    private final List list = new List("Bluetooth Devices", List.EXCLUSIVE);

    /**
     * Constructor
     */
    public Gui(){
        pref = Preferences.getInstance();

        user = new StringItem("","");
        device = new StringItem("","");
        readyForm = new Form("Pinkr ready");
        readyForm.append(user);
        readyForm.append(device);
        readyForm.append("You can press the start button to start a round.");
        readyForm.addCommand(START_ROUND_CMD);
        readyForm.addCommand(CONFIG_CMD);
//        readyForm.addCommand(EXIT_CMD);

        debug = new StringItem("","");
        clock = new StringItem("","");
        startedForm = new Form("Pinkr running");
        startedForm.append(clock);
        startedForm.append(debug);
        startedForm.addCommand(PAUSE_CMD);
//        startedForm.addCommand(STOP_ROUND_CMD);
        startedForm.addCommand(HIDE_CMD);


        duration = new StringItem("","");
        stoppedForm = new Form("Pinkr stopped");
        stoppedForm.append(duration);
        stoppedForm.addCommand(START_ROUND_CMD);
        stoppedForm.addCommand(EXIT_CMD);

        msg = new StringItem("","");
        msgForm = new Form("Message");
        msgForm.append(msg);
    }

    /**
     * Currently used by GPS data receiver to display current possition,
     * speed and other info
     */
    public void debug(String frase) {
        debug.setText(frase);
    }

    /**
     * Set the receiver
     */
    public void setReceiver(Receiver recv){
        this.recv = recv;
        this.recv.setGui(this);
    }

    /**
     * Set the real-time clock of an ongoing round
     */
    public void setClock(String s){
        clock.setText("Timing: " + s + '\n');
    }

    /**
     * Set the final duration of a finished round
     */
    public void setDuration(String s){
        duration.setText("Duration: " + s + '\n');
    }

    /**
     * Set the midlet
     */
    public void setMidlet(PinkrMIDlet parent){
        this.midlet = parent;
        this.display = Display.getDisplay(midlet);
    }

    /**
     * Shows initial splash screen during initialization
     */
    public void showSplash() {
        SplashScreen splash = new SplashScreen();
        display.setCurrent(splash);
    }

    /**
     * Shows a message on screen. Used as waiting screen.
     * @param title
     * @param contents
     */
    public void showMessage(String title, String contents){
        msgForm.setTitle(title);
        msg.setText(contents);
        display.setCurrent(msgForm);
    }

    /**
     * Shows available GPS devices
     */
    public void showDeviceTypesConsole(boolean internalGpsFound, boolean btLocalDeviceFound){
            List list = new List(DeviceTypesMenuTitle, List.IMPLICIT);
            if(internalGpsFound){
                list.append(InternalDeviceMenuItem, null);
            }
            if(btLocalDeviceFound){
                list.append(BtDeviceMenuItem, null);
            }
            list.addCommand(SELECT_CMD);
            list.setCommandListener(this);
            display.setCurrent(list);

            if( (!internalGpsFound) && (!btLocalDeviceFound) ){
                if(noGpsForm == null){
                    noGpsForm = new Form("No GPS device found!");
//                    noGpsForm.addCommand(DISCOVER_CMD);
                    noGpsForm.addCommand(EXIT_CMD);
                    noGpsForm.setCommandListener(this);
                }
                display.setCurrent(noGpsForm);
            }
    }

    /**
     * Shows available bluetooth remote devices
     */
    public void showBtDevicesConsole(Vector names){
        if(names.size() > 0){
            List list = new List(DevicesMenuTitle, List.IMPLICIT);
            int size = names.size();
            for(int i = 0; i < size; i++){
                list.append((String)names.elementAt(i), null);
            }
            list.addCommand(SELECT_CMD);
            list.addCommand(BT_DISCOVER_CMD);
            list.setCommandListener(this);
            display.setCurrent(list);
        }else{
            if(noGpsForm == null){
                noGpsForm = new Form("No bluetooth GPS device found!");
                noGpsForm.addCommand(BT_DISCOVER_CMD);
                noGpsForm.addCommand(EXIT_CMD);
                noGpsForm.setCommandListener(this);
            }
            display.setCurrent(noGpsForm);
        }
        display.flashBacklight(3000);
    }

    /**
     * Called after all initialization is done.
     * Shows <code>debug StringItem</code> on the display
     */
    public void showReadyConsole() {
        user.setText("User: " + pref.get(kUser) + "\n");
        device.setText("Device" + pref.get(kDeviceType) + "\n");
        readyForm.setCommandListener(this);
        display.setCurrent(readyForm);
    }

    /**
     * Called when a round is started
     * TODO reuse the form object
     */
    public void showStartedConsole(){
        startedForm.setCommandListener(this);
        display.setCurrent(startedForm);
    }

    /**
     * Called after a round stopped
     */
    public void showStoppedConsole(){
        duration.setText("Duration: " + recv.getRuntime() + '\n');
        stoppedForm.setCommandListener(this);
        display.setCurrent(stoppedForm);
    }

    /**
     * Hide the app windows and keep running at background.
     * Tested under Symbian S60. May not work on some other operating system.
     */
    public void hide(){
        display.setCurrent(null);
    }

    /**
     * Method called by other parts of the code when something
     * went wrong
     */
    public void showErrorDialog(String msg, boolean vibrate) {
        if (vibrate)
            display.vibrate(1000);
        Alert alert = new Alert("Error", msg, null, AlertType.ERROR);
        alert.setTimeout(Alert.FOREVER);
        display.setCurrent(alert, display.getCurrent());
    }

    /**
     * Listens and acts on commands
     */
    public void commandAction(Command c, Displayable s)  {
        if(c == START_ROUND_CMD){
            midlet.startRound();
            startedForm.removeCommand(RESUME_CMD);
            startedForm.removeCommand(STOP_ROUND_CMD);
            startedForm.addCommand(PAUSE_CMD);
            startedForm.addCommand(HIDE_CMD);
        }
        else if(c == PAUSE_CMD){
            midlet.pauseRound();
            startedForm.removeCommand(PAUSE_CMD);
            startedForm.removeCommand(HIDE_CMD);
            startedForm.addCommand(RESUME_CMD);
            startedForm.addCommand(STOP_ROUND_CMD);
        }
        else if(c == RESUME_CMD){
            midlet.resumeRound();
            startedForm.removeCommand(RESUME_CMD);
            startedForm.removeCommand(STOP_ROUND_CMD);
            startedForm.addCommand(PAUSE_CMD);
            startedForm.addCommand(HIDE_CMD);
        }
        else if(c == STOP_ROUND_CMD){
            midlet.stopRound();
        }
        else if(c == CONFIG_CMD){
            midlet.setPreferences(true);
        }
        else if(c == BT_DISCOVER_CMD){
            midlet.setDeviceType( PinkrMIDlet.BT_DEVICE_TYPE );
        }
        else if(c == SELECT_CMD || c == List.SELECT_COMMAND){
            List l = (List) s;
            // Device Types Menu
            if( l.getTitle().equals(DeviceTypesMenuTitle) ){
                int selectedType = -1;
                String menuItem = l.getString(l.getSelectedIndex());
                if( menuItem.equals(InternalDeviceMenuItem) ){
                    selectedType = PinkrMIDlet.INTERNAL_DEVICE_TYPE;
                    pref.put(kDeviceType, tBuiltin);
                }
                else if( menuItem.equals(BtDeviceMenuItem) ){
                    selectedType = PinkrMIDlet.BT_DEVICE_TYPE;
                    pref.put(kDeviceType, tBluetooth);
                }
                pref.save();
                midlet.setDeviceType( selectedType );
            }

            // Bt Devices Menu
            if( l.getTitle().equals(DevicesMenuTitle) ){
                midlet.setBtRemoteDevice(l.getSelectedIndex());
                pref.put(kBtConnectionURL, midlet.getBtConnectionURL());
                pref.save();
            }
        }
        else if(c == HIDE_CMD){
            hide();
        }
        else if (c == EXIT_CMD) {
            try{
                // terminate
                midlet.terminate();
                // exit midlet
                midlet.destroyApp(true);
                midlet.notifyDestroyed();
            }catch(MIDletStateChangeException e){
                showErrorDialog(e.getMessage(), false);
            }
        }
    }
}
